Cette animation montre l’évolution de l’application depuis le premier commit jusqu’au dernier.
Nous partons d’une représentation assez basique (poser les bases) vers l’ajout progressif d’éléments d’interaction utilisateur, de représentation de la données, ainsi que d’éléments contextuels textes.
Cette animation montre l’évolution de l’appli depuis le premier commit jusqu’au dernier :
Le code de l’application est accessible dans le répertoire github shiny-artif-app.
Pour créer une application Shiny, il faut, depuis RStudio, aller dans File > New File > Shiny Web App...
Cela crée une application basique qui sert d’exemple, avec une liste déroulante et un graphique d’exemple :
library(shiny)
ui <- fluidPage(
# Application title
titlePanel("Old Faithful Geyser Data"), # on changera le nom de l'appli plus tard
sidebarLayout(
sidebarPanel(
"liste"
),
mainPanel(
"résultats"
)
)
)
server <- function(input, output) {
# vide pour le moment
}
# Run the application
shinyApp(ui = ui, server = server)
ui est la partie destinée à accueillir les éléments d’interface (esthétique)server est la partie dans laquelle seront définis les mécanismes d’interaction, d’interrogation et de représentation des données.En quelque sorte, par analogie avec un modèle MVC (Modèle Vue Contrôleur), ui concerne la Vue et server le Contrôleur.
Dans un premier temps, on pose les bases, et on efface certains éléments de l’application d’exemple.
La partie est laissée vide pour le moment :
server <- function(input, output) {
# vide
}
On lit les données de flux :
flux <- read_csv("data/obs_artif_conso_com_2009_2020_V2.csv", na = c("", "NULL")) %>%
filter(idreg == "93")
Ainsi que les données communales :
# Contours de communes
comms <<- readRDS("data/comms.rds") %>%
filter(INSEE_REG == 93)
# Liste communes
communes <- flux$idcom
names(communes) <- glue("{flux$idcomtxt} ({flux$idcom})")
On ajoute la liste déroulante des communes maintenant qu’elles ont été lues :
sidebarLayout(
sidebarPanel(
selectInput("communes", label = NULL, choices = communes, selected = NULL),
),
mainPanel(
"Résultats"
)
)
On peut afficher un tableau des résultats de flux pour une commune sélectionnée, juste pour tester les mécanismes de sélection, notamment la fonction getStatsFlux vue dans un précédent notebook.
mainPanel(
dataTableOutput("tbResults")
)
On ajoute les résultats avec renderDataTable et dataTableOutput :
output$tbResults <- renderDataTable({
codeInsee <- input$communes
df <- flux %>% getStatsFlux(codeInsee)
return(df)
})
On ajoute le streamgraph.
mainPanel(
streamgraphOutput("streamPlot")
)
output$streamPlot <- renderStreamgraph({
myStream <- flux %>% makeStream(codeInsee)
return(myStream)
})
On peut ajouter des informations assez sommaires sur la commune sélectionnée, sous la forme de texte.
mainPanel(
textOutput("txtCommune"),
streamgraphOutput("streamPlot")
)
output$txtCommune <- renderText({
codeInsee <- input$communes
fComm <- flux %>% filter(idcom == codeInsee)
paste(fComm$idcomtxt, fComm$idcom, fComm$artcom0920)
})
Pour la légende, on utilise simplement de l’HTML stylisé, plutôt que la légende du plot :
tagList(
div(
myStream,
style="margin-bottom:20px;"
),
div(
tags$span("Habitat",
style = glue("background-color:{myPalette['blue']};padding:10px;")),
tags$span("Activité",
style = glue("background-color:{myPalette['red']};padding:10px;")),
tags$span("Mixte",
style = glue("background-color:{myPalette['magenta']};padding:10px;")),
tags$span("Inconnu",
style = glue("background-color:{myPalette['grey']};padding:10px;")),
style="text-align:center"
)
)
On peut faire du HTML assez avancé avec R Shiny !
Ici, on intègre un élément de charte graphique Cerema, notamment la palette graphique de l’établissement au format JSON.
On ajoute une nouvelle information en texte.
Les variables de type reactive sont très utiles lorsqu’il s’agit de récupérer à plusieurs endroits le résultat d’une variable calculée car cela évite, entre autres, de devoir répliquer les mécanismes de création de cette variable à ces multiples endroits. Aussi, un mécanisme interne permet de ne pas recalculer la variable si elle n’a pas changé avec les nouveaux critères de création.
fComm <- reactive({
fComm <- flux %>% filter(idcom == input$communes)
return(fComm)
})
On peut récupérer les variables comme ceci :
fComm()$artcom0920
fComm()$nafart0920
leafletOutput("mymap")
output$mymap <- renderLeaflet({
leaflet() %>%
addTiles(group = "OSM")
})
Pour ajuster la carte sur PACA, on utile la fonction fitBounds :
output$mymap <- renderLeaflet({
bb <- st_bbox(comms) %>% as.numeric
leaflet() %>%
addTiles(group = "OSM") %>%
fitBounds(lng1 = bb[1],
lat1 = bb[2],
lng2 = bb[3],
lat2 = bb[4])
})
Ajoutons davantage de fonds cartos, notamment ceux de l’IGN : orthophoto, Plan IGN.
output$mymap <- renderLeaflet({
leaflet() %>%
addTiles(group = "OSM") %>%
addProviderTiles(providers$CartoDB.PositronOnlyLabels, group = "Villes") %>%
addTiles("http://wxs.ign.fr/choisirgeoportail/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&STYLE=normal&TILEMATRIXSET=PM&FORMAT=image/png&LAYER=GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}",
options = c(WMSTileOptions(tileSize = 256),
providerTileOptions(minZoom = 1, maxZoom = 15)),
attribution='<a target="_blank" href="https://www.geoportail.gouv.fr/">Geoportail France</a>',
group = "Plan IGN"
) %>%
addTiles("http://wxs.ign.fr/choisirgeoportail/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&STYLE=normal&TILEMATRIXSET=PM&FORMAT=image/jpeg&LAYER=ORTHOIMAGERY.ORTHOPHOTOS&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}",
options = c(WMSTileOptions(tileSize = 256),
providerTileOptions(minZoom = 1, maxZoom = 22)),
attribution='<a target="_blank" href="https://www.geoportail.gouv.fr/">Geoportail France</a>',
group = "Photo aérienne"
) %>%
addLayersControl(baseGroups = c("Photo aérienne", "Plan IGN", "OSM"),
overlayGroups = "Villes",
options = layersControlOptions(collapsed = FALSE)) %>%
fitBounds(lng1 = bb[1], lat1 = bb[2], lng2 = bb[3], lat2 = bb[4])
})
Pour trouver les coordonnées au clic, on utile un mécanisme de sélection basé sur input, notamment input$mymap.
A noter que l’on peut aussi réagir au clic sur une entité (
input$mymap_shape_click), voire au passage (input$mymap_shape_mouseover). Voir à ce titre la page dédiée sur R Leaflet.
verbatimTextOutput("foo")
Le code INSEE est récupéré dynamiquement après un clic sur la carte.
On rend le code INSEE reactive car il sera utilisé à plusieurs endroits : ici ou encore là
On peut même créer des reactive en cascade, comme ici, pour fComm() qui s’appuie sur codeInsee()
fComm <- reactive({
fComm <- flux %>% filter(idcom == input$communes)
fComm <- flux %>% filter(idcom == codeInsee())
return(fComm)
})
Pour le streamgraph, on utilise codeInsee() comme ceci :
myStream <- flux %>% makeStream(codeInsee())
Quand les données ne sont pas encore chargées, on peut avoir un rendu bizarre avec un message d’erreur.
Du coup, on introduit la fonction
req (pour ‘requiert’) qui exécute la suite seulement si la condition est réalisée :
fComm <- reactive({
req(codeInsee()) # < ici
fComm <- flux %>% filter(idcom == codeInsee())
return(fComm)
})
On crée un proxy à la carte pour réaliser plus tard des actions sur celle-ci (zoomer, ajouter des éléments cartos) sans avoir à recalculer la carte (appelée mymap) à chaque fois :
proxy <- leafletProxy("mymap")
On ajoute le centroïde de la commune cliquée :
observe({
req(codeInsee())
myComm <- comms %>% filter(INSEE_COM == codeInsee())
# Ajout du marqueur
proxy %>%
clearMarkers() %>%
addMarkers(data = myComm %>% st_centroid)
})
On ajoute le contour de la commune (c’est plus sympa) :
proxy %>%
clearShapes() %>%
addPolygons(data = myComm,
color = paletteCerema$secondaire$orange,
weight = 1,
smoothFactor = 0.5,
opacity = 1,
fillOpacity = 0.3,
fillColor = paletteCerema$secondaire$orange,
highlightOptions = highlightOptions(color = paletteCerema$secondaire$orange,
weight = 2,
fillOpacity = 0.1,
bringToFront = TRUE))
Le marqueur s’affiche, le contour aussi, mais parfois la commune semble loin, lo i n, l o i n
Du coup, petite astuce ergonomique : au clic, on va automatiquement vers la commune cliquée :
bb <- st_bbox(myComm)
proxy %>%
flyToBounds(lng1 = as.numeric(bb$xmin),
lat1 = as.numeric(bb$ymin),
lng2 = as.numeric(bb$xmax),
lat2 = as.numeric(bb$ymax))
On peut même changer le comportement de la liste déroulante utilisée au début pour afficher les stats (voir cette ligne) pour aller automatiquement vers la commune cliquée :
observeEvent(input$communes, {
codeInsee <- input$communes
bb <- comms %>% filter(INSEE_COM == codeInsee) %>% st_bbox %>% as.numeric
proxy %>% flyToBounds(lng1 = bb[1], lat1 = bb[2], lng2 = bb[3], lat2 = bb[4])
})
C’est aussi l’occasion d’introduire observeEvent qui ‘écoute’ les évènements, et qui, ici déclenche une action lorsqu’on choisit un élément dans la liste des communes (input$communes)
Généralement, le premier élément d’une liste est une instruction type ‘Veuillez choisir une commune’
Au lieu d’utiliser req (voir ce commit) qui conditionne l’affichage à la sélection d’une commune, nous pouvons générer un message pour préciser qu’il faut cliquer sur la carte lorsqu’aucune commune n’a été cliquée.
Si aucune commune n’est sélectionnée : valeur nulle, alors on affiche ce message (Voir la ligne de code) :
if(is.null(codeInsee())) return(tagList(icon("mouse-pointer"), "Cliquez sur la carte pour afficher les statistiques"))
Tiens, comme on y est, pourquoi pas ajouter une treemap.
plotlyOutput("treemap")
output$treemap <- renderPlotly({
req(codeInsee())
flux %>% makeTreemap(codeInsee())
})
ui.R, server.R et global.RLorsque le code d’une application devient conséquent, cela peut être utile, plutôt que d’utiliser un unique fichier app.R, de séparer le contenu dans trois fichiers : ui.R, server.R et global.R
ui.R comprend les éléments d’interface auparavant contenus dans ui <- fluidPage({...})server.R comprend la logique applicative et fonctionnelle de l’application : mécanismes d’interrogation et de représentation des données, auparavant contenue dans server <- function(input, output) {...}global.R comprend les variables utilisées dans l’application.global.RLes variables contenues dans global.R deviennent automatiquement globales.
Il n’y a alors plus besoin d’utiliser la double flèche d’assignation des variables globales dans global.R (précédemment monNom <<- "Mathieu"). On a donc monNom <- "Mathieu" dans global.R
Comme nous venons de le voir, le développement de l’application Shiny se concentre avant sur la donnée, si bien qu’une application Shiny peut tout à fait être développée sans connaissance du HTML, ou du CSS (bien que leur connaissance soit un pré-requis pour l’esthétisation du site).
Le contenu, à savoir la donnée, occupe une place centrale, et le développement du contenant, son habillage peuvent être opérés a posteriori.
Le développement peut être réalisé par un statisticien. Un ergonome peut ensuite définir le UX Design, à savoir le design de l’interface et de ses interactions. Enfin, un graphiste peut venir habiller le site d’une certaine charte graphique.
Comme nous l’avons vu dans les notebooks 1 à 7, beaucoup de préparation est nécessaire avant d’aboutir à l’application : préparation des données, voire aussi préparation des mécanismes (fonctions) d’interrogation des données ou de représentation de celles-ci.
Le développement de cette dataviz a suivi ces différentes étapes :
Bien sûr, on ne peut pas représenter des choses qui n’existent pas, d’où l’importance de la collecte, et de la stabilité du point d’accès à la donnée, notamment lorsque votre dataviz doit être mise à jour régulièrement.
Une dataviz faite sur des données de mauvaise qualité produira des graphiques de mauvaise qualité.
Une certaine sémiologie graphique et rigueur doit être suivie afin de restituer de manière convenable l’information à l’utilisateur.
L’expérience utilisateur doit être prise en compte lors du développement, que ce soit dans la manipulation de l’interface ou la lecture des graphiques.
Rendre fonctionnel et généralisable les mécanismes sous la forme de fonctions permet déjà de rendre le code plus parlant, plus léger à la lecture, plus pérenne et appropriable par une tierce personne, mais aussi de les utiliser ailleurs, dans d’autres applis.
Pas mal de temps peut être passé dans l’esthétique. Le respect d’une charte graphique, l’esthétisation est souvent le premier désir de celui qui vous demandera une application de dataviz.
Enfin, le refactoring vise à améliorer les commentaires dans le code, réorganiser certaines fonctions, certains blocs de code pour faciliter la lecture et la pérénnité du code informatique, son appropriation par d’autres, ou par vous-même (lorsque le vent vous aura mené entre temps vers de multiples applis et que vous l’aurez un peu oubliée !)
Voici selon moi les dimensions d’une dataviz :
Dans les cas 1 et 2, l’information est intéressante, de valeur.
Dans le premier cas (cas 1), oin s’attache davantage à l’accessibilité de la dataviz, le fait qu’elle puisse être lue et interprétée facilement, alors que le second cas est celui d’une dataviz sophistiquée dans sa manière de rendre, et sans doute créative, mais qui ne mise pas vraiment sur l’accessibilité de la représentation.
Le second cas est souvent privilégié par ceux qui s’intéressent aux représentations abstraites et artistiques réalisées avec de la donnée, à savoir celles réalisées par des data artists, comme on serait tentés de les appeler.
La première stratégie est sans doute celle à adopter par d’institutions publiques, car privilégiant la transmission et l’intelligibilité des informations à diffuser.